<?php

/**
 * AbstractBeanFactory 
 * 
 * @uses IBeanFactory
 * @abstract
 * @package plugins.sfIocPlugin
 * @version $id$
 * @copyright 2008
 * @author Marcin Nowak <marcin.j.nowak@gmail.com> 
 * @license LGPL
 */
abstract class AbstractBeanFactory implements IBeanFactory
{
    protected $beans = array(); /* array of bean definitions */
    private $instances = array();
    protected $parent_bean_factory = null;

    public function getBean($name, $class=null, array $prototype_args = null)
    {
        $mergedDefinition = null;
        try
        {
            $mergedDefinition = $this->getMergedBeanDefinition($name, false);
        }
        catch (NoSuchBeanDefinitionException $e)
        {
            if($this->parent_bean_factory) return $this->parent_bean_factory->getBean($name, $class);
            throw $e;
        }

        if($this->isSingleton($name))
        {
            if(!isset($this->instances[$name]))
            {
                $this->instances[$name] = $this->createBean($name, $mergedDefinition, $prototype_args);
            }
            return $this->instances[$name];
        }

        if(!$this->isPrototype($name)) throw new BeansException('Unknown bean type for "'.$name.'"');

        return $this->createBean($name, $mergedDefinition, $prototype_args);

    }

    public function containsBean($name)
    {
        return isset($this->beans[$name]);
    }

    public function isPrototype($name)
    {
        if($this->containsBean($name)) return $this->beans[$name]['singleton'] === false;
        throw new NoSuchBeanDefinitionException('Unknown bean name: '.$name);
    }

    public function isSingleton($name)
    {
        if($this->containsBean($name)) return $this->beans[$name]['singleton'] === true;
        throw new NoSuchBeanDefinitionException('Unknown bean name: '.$name);
    }

    public function getType($name)
    {
        if($this->containsBean($name)) return $this->beans[$name]['class'];
        throw new NoSuchBeanDefinitionException('Unknown bean name: '.$name);
    }

    public function getParentBeanFactory()
    {
        return $this->parent_bean_factory;
    }

    public function getMergedBeanDefinition($beanName, $definition)
    {
        if(is_array($definition))
        {
            return sfToolkit::arrayDeepMerge($this->getBeanDefinition($beanName), $definition);
        }
        try
        {
            return $this->getMergedBeanDefinition($beanName, $this->getBeanDefinition($beanName));
        }
        catch (NoSuchBeanDefinitionException $ex) 
        {
            if ($definition && $this->getParentBeanFactory() instanceof AbstractBeanFactory) 
            {
                return $this->getParentBeanFactory()->getMergedBeanDefinition($beanName, true);
            }
            else 
            {
                throw $ex;
            }
        }
    }

    public function getBeanDefinition($name)
    {
        if($this->containsBean($name)) return $this->beans[$name];
        throw new NoSuchBeanDefinitionException('Unknown bean name: '.$name);
    }

    protected function createBean($name, $definition, array $prototype_args = null)
    {
        /* fixme: support for namespaces */
        $class_name = basename(str_replace('.','/',$definition['class']));
        $is_singleton = $definition['singleton'];
        $parameters = isset($definition['construct_params']) ? $definition['construct_params'] : array();

        $construct_parameters = array();
        if(sizeof($parameters))
        {
            $use_prototype_args = $is_singleton===false && $prototype_args;
            /*
            if($use_prototype_args && sizeof($parameters) !== sizeof($prototype_args))
            {
                throw new BeansException('Incorrect count of custom arguments for prototype bean');
            }
             */
            foreach($parameters as $parameter)
            {
                if($parameter instanceof BeanDefinitionReference)
                {
                $parameter_class = (string) $parameter;
                $parameter_ref_id = (string) $parameter;

                $construct_parameters[] = $use_prototype_args && sizeof($prototype_args) ? array_shift($prototype_args) : $this->getBean($parameter_ref_id, $parameter_class);
                }
                else if(is_array($parameter))
                {
                    $construct_parameters[] = $parameter;
                }
                else /* simple value */
                {
                    $construct_parameters[] = $use_prototype_args && sizeof($prototype_args) ? array_shift($prototype_args) : $parameter;
                }
            }

            $classInfo = new ReflectionClass($class_name);
            /* type checking - chyba wyrzucic
            foreach($classParameters=$classInfo->getParameters() as $key =>$classParameter)
            {
                $classParameterClass = $classParameter->getClass();
                if ($classParameterClass && 
                    !$construct_parameters[$key] instanceof $classParameterClass->getName()
                ) 
                {
                    throw new BeansException(sprintf('%s bean parameter "%s" require instance of %s class',
                        $name, $classParameter->getName(), $classParameter->getClass()));
                }
            }
             */
            return $classInfo->newInstanceArgs($construct_parameters);
        }
        return new $class_name;
    }

}
